# @Time    : 2025/7/28 03:38
# @Author  : Lennert
# @FileName: filter_by_name.py
# @Description : 整合自 name_filter.py 的函数名的模糊匹配逻辑

import logging
import re

from bson import ObjectId
from rapidfuzz import fuzz

from config.config import MatchType
from utils.mongo_utils import get_npm_collection,get_arkts_collection
# 模糊匹配阈值
NAME_SIMILARITY_THRESHOLD = 60

# 候选集大小限制
# 注意：name_filter.py 中的 MAX_CANDIDATES 是根据 arkts_name 长度动态调整的
# 我们在这里定义一个基础值，具体逻辑在 get_npm_candidates_fuzzy 中实现
MAX_CANDIDATES_BASE = 3000  # 可以定义一个基础值，但实际由函数决定


def get_best_name_match_from_ids(match_type, candidate_ids, target_name):
    """
    从给定的候选ID列表中，找出与目标名称最相似的候选项

    Args:
        candidate_ids: ObjectId,候选ID列表（从high_similarity_candidates中提取）
        target_name: 目标名称（arkts_name）
        match_type: 匹配过程类型

    Returns:
        str: 最佳匹配的候选ID，如果没有达到阈值则返回None
    """
    if not candidate_ids or not target_name:
        return None
    try:
        match_functions = None
        if match_type == MatchType.TPL_MATCH:
            match_functions = get_npm_collection()
        elif match_type == MatchType.TPL_VERSION_MATCH:
            match_functions = get_arkts_collection()
        # 批量查询这些ID对应的name字段
        matching_docs = match_functions.find(
            {'_id': {'$in': candidate_ids}},
            {'_id': 1, 'name': 1}
        )

        # 计算名称相似度
        best_match_id = None
        best_similarity = 0
        target_name_lower = target_name.lower()

        for doc in matching_docs:
            npm_name = doc.get('name', '').strip()
            if not npm_name:
                continue

            similarity_score = fuzz.ratio(target_name_lower, npm_name.lower())

            if similarity_score > best_similarity:
                best_similarity = similarity_score
                best_match_id = str(doc['_id'])

        # 只有超过阈值才返回
        return best_match_id if best_similarity >= NAME_SIMILARITY_THRESHOLD else None

    except Exception as e:
        logging.error(f"从ID列表匹配名称时出错: {e}")
        return None


def build_npm_base_query_fuzzy(arkts_name):
    base_query = {'ast_feature': {'$exists': True}}
    if arkts_name and arkts_name.lower() != 'anonymous':
        if len(arkts_name) >= 4:
            # 对于较长的名称，使用更精确的匹配
            if len(arkts_name) > 7:
                prefix = arkts_name[:4]
                base_query['name'] = {'$regex': f'^{re.escape(prefix)}', '$options': 'i'}
            else:
                # 对于中等长度，使用前缀或后缀
                prefix = arkts_name[:4]
                suffix = arkts_name[-3:] if len(arkts_name) > 2 else prefix

                if prefix != suffix:
                    base_query['$or'] = [
                        {'name': {'$regex': f'^{re.escape(prefix)}', '$options': 'i'}},
                        {'name': {'$regex': f'{re.escape(suffix)}$', '$options': 'i'}}
                    ]
                else:
                    base_query['name'] = {'$regex': f'^{re.escape(prefix)}', '$options': 'i'}
        else:
            # 对于很短的名称，放宽匹配条件
            base_query['name'] = {'$regex': re.escape(arkts_name), '$options': 'i'}
    return base_query


def get_npm_candidates_fuzzy(arkts_name):
    """获取 NPM 候选函数 (复制并修改自 name_filter.py)"""
    try:

        npm_functions =get_npm_collection()

        base_query = build_npm_base_query_fuzzy(arkts_name)

        # 动态设置候选集大小限制
        length = len(arkts_name)
        if length < 3:
            max_candidates = 1000000
        elif 3 <= length <= 6:
            max_candidates = 100000
        else:
            max_candidates = 10000

        npm_candidates_cursor = npm_functions.find(
            base_query,
            {'_id': 1, 'name': 1}
        ).limit(max_candidates)

        return list(npm_candidates_cursor)
    except Exception as e:
        logging.error(f"获取 NPM 候选函数时出错: {e}")
        return []


def filter_candidates_by_name_similarity(arkts_name, npm_candidates):
    """
    核心匹配函数：使用 fuzz.Ratio 计算相似度，找出所有高于阈值的匹配项。
    (复制自 name_filter.py 的 test_with_ratio_method 并稍作修改)
    Returns:
        list: 所有满足条件的匹配项的 ID 字符串列表 (已按分数降序排序)。
    """
    matched_candidate_ids = []
    arkts_name_lower = arkts_name.lower()
    arkts_len = len(arkts_name_lower)

    for npm_doc in npm_candidates:
        npm_id_str = str(npm_doc['_id'])
        npm_name = npm_doc.get('name', '').strip()

        if not npm_name:
            continue

        npm_name_lower = npm_name.lower()
        length_diff = abs(len(npm_name_lower) - arkts_len)

        # 早期剪枝：长度差异过大则跳过
        if length_diff > 15:
            continue

        similarity_score = fuzz.ratio(arkts_name_lower, npm_name_lower)

        if similarity_score >= NAME_SIMILARITY_THRESHOLD:
            # 只存储 ID 和分数，用于排序
            matched_candidate_ids.append((npm_id_str, similarity_score))

    # 按相似度分数降序排序
    matched_candidate_ids.sort(key=lambda x: x[1], reverse=True)

    # 只返回 ID 列表
    return [item[0] for item in matched_candidate_ids]
